/**
 * Copyright © 2023-2030 The ruanrongman Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package top.rslly.iot.utility.ai.voice.concentus;

/// <summary>
/// Normalized line spectrum frequency processor
/// </summary>
class NLSF {

  private static final int MAX_STABILIZE_LOOPS = 20;

  private static final int QA = 16;

  /// <summary>
  /// Number of binary divisions, when not in low complexity mode
  /// </summary>
  private static final int BIN_DIV_STEPS_A2NLSF = 3;
  /* must be no higher than 16 - log2( LSF_COS_TAB_SZ ) */

  private static final int MAX_ITERATIONS_A2NLSF = 30;

  /// <summary>
  /// Compute quantization errors for an LPC_order element input vector for a VQ codebook
  /// </summary>
  /// <param name="err_Q26">(O) Quantization errors [K]</param>
  /// <param name="in_Q15">(I) Input vectors to be quantized [LPC_order]</param>
  /// <param name="pCB_Q8">(I) Codebook vectors [K*LPC_order]</param>
  /// <param name="K">(I) Number of codebook vectors</param>
  /// <param name="LPC_order">(I) Number of LPCs</param>
  static void silk_NLSF_VQ(int[] err_Q26, short[] in_Q15, short[] pCB_Q8, int K, int LPC_order) {
    int diff_Q15, sum_error_Q30, sum_error_Q26;
    int pCB_idx = 0;

    Inlines.OpusAssert(err_Q26 != null);
    Inlines.OpusAssert(LPC_order <= 16);
    Inlines.OpusAssert((LPC_order & 1) == 0);

    // Loop over codebook
    for (int i = 0; i < K; i++) {
      sum_error_Q26 = 0;

      for (int m = 0; m < LPC_order; m += 2) {
        // Compute weighted squared quantization error for index m
        diff_Q15 = Inlines.silk_SUB_LSHIFT32(in_Q15[m], pCB_Q8[pCB_idx++], 7); // range: [ -32767 :
                                                                               // 32767 ]
        sum_error_Q30 = Inlines.silk_SMULBB(diff_Q15, diff_Q15);

        // Compute weighted squared quantization error for index m + 1
        diff_Q15 = Inlines.silk_SUB_LSHIFT32(in_Q15[m + 1], pCB_Q8[pCB_idx++], 7); // range: [
                                                                                   // -32767 : 32767
                                                                                   // ]
        sum_error_Q30 = Inlines.silk_SMLABB(sum_error_Q30, diff_Q15, diff_Q15);

        sum_error_Q26 = Inlines.silk_ADD_RSHIFT32(sum_error_Q26, sum_error_Q30, 4);

        Inlines.OpusAssert(sum_error_Q26 >= 0);
        Inlines.OpusAssert(sum_error_Q30 >= 0);
      }

      err_Q26[i] = sum_error_Q26;
    }
  }

  /// <summary>
  /// Laroia low complexity NLSF weights
  /// </summary>
  /// <param name="pNLSFW_Q_OUT">(O) Pointer to input vector weights [D]</param>
  /// <param name="pNLSF_Q15">(I) Pointer to input vector [D]</param>
  /// <param param name="D">(I) Input vector dimension (even)</param>
  static void silk_NLSF_VQ_weights_laroia(short[] pNLSFW_Q_OUT, short[] pNLSF_Q15, int D) {
    int k;
    int tmp1_int, tmp2_int;

    Inlines.OpusAssert(pNLSFW_Q_OUT != null);
    Inlines.OpusAssert(D > 0);
    Inlines.OpusAssert((D & 1) == 0);

    // First value
    tmp1_int = Inlines.silk_max_int(pNLSF_Q15[0], 1);
    tmp1_int = Inlines.silk_DIV32((int) 1 << (15 + SilkConstants.NLSF_W_Q), tmp1_int);
    tmp2_int = Inlines.silk_max_int(pNLSF_Q15[1] - pNLSF_Q15[0], 1);
    tmp2_int = Inlines.silk_DIV32((int) 1 << (15 + SilkConstants.NLSF_W_Q), tmp2_int);
    pNLSFW_Q_OUT[0] = (short) Inlines.silk_min_int(tmp1_int + tmp2_int, Short.MAX_VALUE);

    Inlines.OpusAssert(pNLSFW_Q_OUT[0] > 0);

    // Main loop
    for (k = 1; k < D - 1; k += 2) {
      tmp1_int = Inlines.silk_max_int(pNLSF_Q15[k + 1] - pNLSF_Q15[k], 1);
      tmp1_int = Inlines.silk_DIV32((int) 1 << (15 + SilkConstants.NLSF_W_Q), tmp1_int);
      pNLSFW_Q_OUT[k] = (short) Inlines.silk_min_int(tmp1_int + tmp2_int, Short.MAX_VALUE);
      Inlines.OpusAssert(pNLSFW_Q_OUT[k] > 0);

      tmp2_int = Inlines.silk_max_int(pNLSF_Q15[k + 2] - pNLSF_Q15[k + 1], 1);
      tmp2_int = Inlines.silk_DIV32((int) 1 << (15 + SilkConstants.NLSF_W_Q), tmp2_int);
      pNLSFW_Q_OUT[k + 1] = (short) Inlines.silk_min_int(tmp1_int + tmp2_int, Short.MAX_VALUE);
      Inlines.OpusAssert(pNLSFW_Q_OUT[k + 1] > 0);
    }

    // Last value
    tmp1_int = Inlines.silk_max_int((1 << 15) - pNLSF_Q15[D - 1], 1);
    tmp1_int = Inlines.silk_DIV32((int) 1 << (15 + SilkConstants.NLSF_W_Q), tmp1_int);
    pNLSFW_Q_OUT[D - 1] = (short) Inlines.silk_min_int(tmp1_int + tmp2_int, Short.MAX_VALUE);

    Inlines.OpusAssert(pNLSFW_Q_OUT[D - 1] > 0);
  }

  /// <summary>
  /// Returns RD value in Q30
  /// </summary>
  /// <param name="x_Q10">(O) Output [ order ]</param>
  /// <param name="indices">(I) Quantization indices [ order ]</param>
  /// <param name="pred_coef_Q8">(I) Backward predictor coefs [ order ]</param>
  /// <param name="quant_step_size_Q16">(I) Quantization step size</param>
  /// <param name="order">(I) Number of input values</param>
  static void silk_NLSF_residual_dequant(
      short[] x_Q10,
      byte[] indices,
      int indices_ptr,
      short[] pred_coef_Q8,
      int quant_step_size_Q16,
      short order) {
    int i, pred_Q10;
    short out_Q10;

    out_Q10 = 0;
    for (i = order - 1; i >= 0; i--) {
      pred_Q10 = Inlines.silk_RSHIFT(Inlines.silk_SMULBB(out_Q10, (short) pred_coef_Q8[i]), 8);
      out_Q10 = Inlines.silk_LSHIFT16((short) indices[indices_ptr + i], 10);
      if (out_Q10 > 0) {
        out_Q10 = Inlines.silk_SUB16(
            out_Q10, (short) (((int) ((SilkConstants.NLSF_QUANT_LEVEL_ADJ) * ((long) 1 << (10))
                + 0.5))/* Inlines.SILK_CONST(SilkConstants.NLSF_QUANT_LEVEL_ADJ, 10) */));
      } else if (out_Q10 < 0) {
        out_Q10 = Inlines.silk_ADD16(
            out_Q10, (short) (((int) ((SilkConstants.NLSF_QUANT_LEVEL_ADJ) * ((long) 1 << (10))
                + 0.5))/* Inlines.SILK_CONST(SilkConstants.NLSF_QUANT_LEVEL_ADJ, 10) */));
      }
      out_Q10 = (short) (Inlines.silk_SMLAWB(pred_Q10, (int) out_Q10, quant_step_size_Q16));
      x_Q10[i] = out_Q10;
    }
  }

  /// <summary>
  /// Unpack predictor values and indices for entropy coding tables
  /// </summary>
  /// <param name="ec_ix">(O) Indices to entropy tables [ LPC_ORDER ]</param>
  /// <param name="pred_Q8">(O) LSF predictor [ LPC_ORDER ]</param>
  /// <param name="psNLSF_CB">(I) Codebook object</param>
  /// <param name="CB1_index">(I) Index of vector in first LSF codebook</param>
  static void silk_NLSF_unpack(short[] ec_ix, short[] pred_Q8, NLSFCodebook psNLSF_CB,
      int CB1_index) {
    int i;
    short entry;
    short[] ec_sel = psNLSF_CB.ec_sel;
    int ec_sel_ptr = CB1_index * psNLSF_CB.order / 2;

    for (i = 0; i < psNLSF_CB.order; i += 2) {
      entry = ec_sel[ec_sel_ptr];
      ec_sel_ptr++;
      ec_ix[i] = (short) (Inlines.silk_SMULBB(Inlines.silk_RSHIFT(entry, 1) & 7,
          2 * SilkConstants.NLSF_QUANT_MAX_AMPLITUDE + 1));
      pred_Q8[i] = psNLSF_CB.pred_Q8[i + (entry & 1) * (psNLSF_CB.order - 1)];
      ec_ix[i + 1] = (short) (Inlines.silk_SMULBB(Inlines.silk_RSHIFT(entry, 5) & 7,
          2 * SilkConstants.NLSF_QUANT_MAX_AMPLITUDE + 1));
      pred_Q8[i + 1] =
          psNLSF_CB.pred_Q8[i + (Inlines.silk_RSHIFT(entry, 4) & 1) * (psNLSF_CB.order - 1) + 1];
    }
  }

  /// <summary>
  /// NLSF stabilizer, for a single input data vector
  /// </summary>
  /// <param name="NLSF_Q15">(I/O) Unstable/stabilized normalized LSF vector in Q15 [L]</param>
  /// <param name="NDeltaMin_Q15">(I) Min distance vector, NDeltaMin_Q15[L] must be >= 1
  /// [L+1]</param>
  /// <param name="L">(I) Number of NLSF parameters in the input vector</param>
  static void silk_NLSF_stabilize(short[] NLSF_Q15, short[] NDeltaMin_Q15, int L) {
    int i, I = 0, k, loops;
    short center_freq_Q15;
    int diff_Q15, min_diff_Q15, min_center_Q15, max_center_Q15;

    // This is necessary to ensure an output within range of a short
    Inlines.OpusAssert(NDeltaMin_Q15[L] >= 1);

    for (loops = 0; loops < MAX_STABILIZE_LOOPS; loops++) {
      /**
       * ***********************
       */
      /* Find smallest distance */
      /**
       * ***********************
       */
      // First element
      min_diff_Q15 = NLSF_Q15[0] - NDeltaMin_Q15[0];
      I = 0;

      // Middle elements
      for (i = 1; i <= L - 1; i++) {
        diff_Q15 = NLSF_Q15[i] - (NLSF_Q15[i - 1] + NDeltaMin_Q15[i]);
        if (diff_Q15 < min_diff_Q15) {
          min_diff_Q15 = diff_Q15;
          I = i;
        }
      }

      // Last element
      diff_Q15 = (1 << 15) - (NLSF_Q15[L - 1] + NDeltaMin_Q15[L]);
      if (diff_Q15 < min_diff_Q15) {
        min_diff_Q15 = diff_Q15;
        I = L;
      }

      /**
       * ************************************************
       */
      /* Now check if the smallest distance non-negative */
      /**
       * ************************************************
       */
      if (min_diff_Q15 >= 0) {
        return;
      }

      if (I == 0) {
        // Move away from lower limit
        NLSF_Q15[0] = NDeltaMin_Q15[0];
      } else if (I == L) {
        // Move away from higher limit
        NLSF_Q15[L - 1] = (short) ((1 << 15) - NDeltaMin_Q15[L]);
      } else {
        // Find the lower extreme for the location of the current center frequency
        min_center_Q15 = 0;
        for (k = 0; k < I; k++) {
          min_center_Q15 += NDeltaMin_Q15[k];
        }

        min_center_Q15 += Inlines.silk_RSHIFT(NDeltaMin_Q15[I], 1);

        // Find the upper extreme for the location of the current center frequency
        max_center_Q15 = 1 << 15;
        for (k = L; k > I; k--) {
          max_center_Q15 -= NDeltaMin_Q15[k];
        }

        max_center_Q15 -= Inlines.silk_RSHIFT(NDeltaMin_Q15[I], 1);

        // Move apart, sorted by value, keeping the same center frequency
        center_freq_Q15 = (short) (Inlines.silk_LIMIT_32(
            Inlines.silk_RSHIFT_ROUND((int) NLSF_Q15[I - 1] + (int) NLSF_Q15[I], 1),
            min_center_Q15, max_center_Q15));
        NLSF_Q15[I - 1] = (short) (center_freq_Q15 - Inlines.silk_RSHIFT(NDeltaMin_Q15[I], 1));
        NLSF_Q15[I] = (short) (NLSF_Q15[I - 1] + NDeltaMin_Q15[I]);
      }
    }

    // Safe and simple fall back method, which is less ideal than the above
    if (loops == MAX_STABILIZE_LOOPS) {
      Sort.silk_insertion_sort_increasing_all_values_int16(NLSF_Q15, L);

      // First NLSF should be no less than NDeltaMin[0]
      NLSF_Q15[0] = (short) (Inlines.silk_max_int(NLSF_Q15[0], NDeltaMin_Q15[0]));

      // Keep delta_min distance between the NLSFs
      for (i = 1; i < L; i++) {
        NLSF_Q15[i] =
            (short) (Inlines.silk_max_int(NLSF_Q15[i], NLSF_Q15[i - 1] + NDeltaMin_Q15[i]));
      }

      // Last NLSF should be no higher than 1 - NDeltaMin[L]
      NLSF_Q15[L - 1] =
          (short) (Inlines.silk_min_int(NLSF_Q15[L - 1], (1 << 15) - NDeltaMin_Q15[L]));

      // Keep NDeltaMin distance between the NLSFs
      for (i = L - 2; i >= 0; i--) {
        NLSF_Q15[i] =
            (short) (Inlines.silk_min_int(NLSF_Q15[i], NLSF_Q15[i + 1] - NDeltaMin_Q15[i + 1]));
      }
    }
  }

  /// <summary>
  /// NLSF vector decoder
  /// </summary>
  /// <param name="pNLSF_Q15">(O) Quantized NLSF vector [ LPC_ORDER ]</param>
  /// <param name="NLSFIndices">(I) Codebook path vector [ LPC_ORDER + 1 ]</param>
  /// <param name="psNLSF_CB">(I) Codebook object</param>
  static void silk_NLSF_decode(short[] pNLSF_Q15, byte[] NLSFIndices, NLSFCodebook psNLSF_CB) {
    int i;
    short[] pred_Q8 = new short[psNLSF_CB.order];
    short[] ec_ix = new short[psNLSF_CB.order];
    short[] res_Q10 = new short[psNLSF_CB.order];
    short[] W_tmp_QW = new short[psNLSF_CB.order];
    int W_tmp_Q9, NLSF_Q15_tmp;

    // Decode first stage
    short[] pCB = psNLSF_CB.CB1_NLSF_Q8;
    int pCB_element = NLSFIndices[0] * psNLSF_CB.order;

    for (i = 0; i < psNLSF_CB.order; i++) {
      pNLSF_Q15[i] = Inlines.silk_LSHIFT16((short) pCB[pCB_element + i], 7);
    }

    // Unpack entropy table indices and predictor for current CB1 index
    silk_NLSF_unpack(ec_ix, pred_Q8, psNLSF_CB, NLSFIndices[0]);

    // Predictive residual dequantizer
    silk_NLSF_residual_dequant(res_Q10,
        NLSFIndices,
        1,
        pred_Q8,
        psNLSF_CB.quantStepSize_Q16,
        psNLSF_CB.order);

    // Weights from codebook vector
    silk_NLSF_VQ_weights_laroia(W_tmp_QW, pNLSF_Q15, psNLSF_CB.order);

    // Apply inverse square-rooted weights and add to output
    for (i = 0; i < psNLSF_CB.order; i++) {
      W_tmp_Q9 = Inlines
          .silk_SQRT_APPROX(Inlines.silk_LSHIFT((int) W_tmp_QW[i], 18 - SilkConstants.NLSF_W_Q));
      NLSF_Q15_tmp = Inlines.silk_ADD32(pNLSF_Q15[i],
          Inlines.silk_DIV32_16(Inlines.silk_LSHIFT((int) res_Q10[i], 14), (short) (W_tmp_Q9)));
      pNLSF_Q15[i] = (short) (Inlines.silk_LIMIT(NLSF_Q15_tmp, 0, 32767));
    }

    // NLSF stabilization
    silk_NLSF_stabilize(pNLSF_Q15, psNLSF_CB.deltaMin_Q15, psNLSF_CB.order);
  }

  /// <summary>
  /// Delayed-decision quantizer for NLSF residuals
  /// </summary>
  /// <param name="indices">(O) Quantization indices [ order ]</param>
  /// <param name="x_Q10">(O) Input [ order ]</param>
  /// <param name="w_Q5">(I) Weights [ order ] </param>
  /// <param name="pred_coef_Q8">(I) Backward predictor coefs [ order ]</param>
  /// <param name="ec_ix">(I) Indices to entropy coding tables [ order ]</param>
  /// <param name="ec_rates_Q5">(I) Rates []</param>
  /// <param name="quant_step_size_Q16">(I) Quantization step size</param>
  /// <param name="inv_quant_step_size_Q6">(I) Inverse quantization step size</param>
  /// <param name="mu_Q20">(I) R/D tradeoff</param>
  /// <param name="order">(I) Number of input values</param>
  /// <returns>RD value in Q25</returns>
  /// Fixme: Optimize this method!
  static int silk_NLSF_del_dec_quant(
      byte[] indices,
      short[] x_Q10,
      short[] w_Q5,
      short[] pred_coef_Q8,
      short[] ec_ix,
      short[] ec_rates_Q5,
      int quant_step_size_Q16,
      short inv_quant_step_size_Q6,
      int mu_Q20,
      short order) {
    int i, j, nStates, ind_tmp, ind_min_max, ind_max_min, in_Q10, res_Q10;
    int pred_Q10, diff_Q10, out0_Q10, out1_Q10, rate0_Q5, rate1_Q5;
    int RD_tmp_Q25, min_Q25, min_max_Q25, max_min_Q25, pred_coef_Q16;
    int[] ind_sort = new int[SilkConstants.NLSF_QUANT_DEL_DEC_STATES];
    byte[][] ind = new byte[SilkConstants.NLSF_QUANT_DEL_DEC_STATES][];
    for (i = 0; i < SilkConstants.NLSF_QUANT_DEL_DEC_STATES; i++) {
      ind[i] = new byte[SilkConstants.MAX_LPC_ORDER];
    }

    short[] prev_out_Q10 = new short[2 * SilkConstants.NLSF_QUANT_DEL_DEC_STATES];
    int[] RD_Q25 = new int[2 * SilkConstants.NLSF_QUANT_DEL_DEC_STATES];
    int[] RD_min_Q25 = new int[SilkConstants.NLSF_QUANT_DEL_DEC_STATES];
    int[] RD_max_Q25 = new int[SilkConstants.NLSF_QUANT_DEL_DEC_STATES];
    int rates_Q5;

    int[] out0_Q10_table = new int[2 * SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT];
    int[] out1_Q10_table = new int[2 * SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT];

    for (i = 0
        - SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT; i <= SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT
            - 1; i++) {
      out0_Q10 = Inlines.silk_LSHIFT(i, 10);
      out1_Q10 = Inlines.silk_ADD16((short) (out0_Q10), (short) 1024);

      if (i > 0) {
        out0_Q10 =
            Inlines.silk_SUB16((short) (out0_Q10),
                (short) (((int) ((SilkConstants.NLSF_QUANT_LEVEL_ADJ) * ((long) 1 << (10))
                    + 0.5))/* Inlines.SILK_CONST(SilkConstants.NLSF_QUANT_LEVEL_ADJ, 10) */));
        out1_Q10 =
            Inlines.silk_SUB16((short) (out1_Q10),
                (short) (((int) ((SilkConstants.NLSF_QUANT_LEVEL_ADJ) * ((long) 1 << (10))
                    + 0.5))/* Inlines.SILK_CONST(SilkConstants.NLSF_QUANT_LEVEL_ADJ, 10) */));
      } else if (i == 0) {
        out1_Q10 =
            Inlines.silk_SUB16((short) (out1_Q10),
                (short) (((int) ((SilkConstants.NLSF_QUANT_LEVEL_ADJ) * ((long) 1 << (10))
                    + 0.5))/* Inlines.SILK_CONST(SilkConstants.NLSF_QUANT_LEVEL_ADJ, 10) */));
      } else if (i == -1) {
        out0_Q10 =
            Inlines.silk_ADD16((short) (out0_Q10),
                (short) (((int) ((SilkConstants.NLSF_QUANT_LEVEL_ADJ) * ((long) 1 << (10))
                    + 0.5))/* Inlines.SILK_CONST(SilkConstants.NLSF_QUANT_LEVEL_ADJ, 10) */));
      } else {
        out0_Q10 =
            Inlines.silk_ADD16((short) (out0_Q10),
                (short) (((int) ((SilkConstants.NLSF_QUANT_LEVEL_ADJ) * ((long) 1 << (10))
                    + 0.5))/* Inlines.SILK_CONST(SilkConstants.NLSF_QUANT_LEVEL_ADJ, 10) */));
        out1_Q10 =
            Inlines.silk_ADD16((short) (out1_Q10),
                (short) (((int) ((SilkConstants.NLSF_QUANT_LEVEL_ADJ) * ((long) 1 << (10))
                    + 0.5))/* Inlines.SILK_CONST(SilkConstants.NLSF_QUANT_LEVEL_ADJ, 10) */));
      }

      out0_Q10_table[i + SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT] =
          Inlines.silk_SMULWB((int) out0_Q10, quant_step_size_Q16);
      out1_Q10_table[i + SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT] =
          Inlines.silk_SMULWB((int) out1_Q10, quant_step_size_Q16);
    }

    Inlines.OpusAssert((SilkConstants.NLSF_QUANT_DEL_DEC_STATES
        & (SilkConstants.NLSF_QUANT_DEL_DEC_STATES - 1)) == 0); // must be power of two

    nStates = 1;
    RD_Q25[0] = 0;
    prev_out_Q10[0] = 0;

    for (i = order - 1;; i--) {
      pred_coef_Q16 = Inlines.silk_LSHIFT((int) pred_coef_Q8[i], 8);
      in_Q10 = x_Q10[i];

      for (j = 0; j < nStates; j++) {
        pred_Q10 = Inlines.silk_SMULWB(pred_coef_Q16, prev_out_Q10[j]);
        res_Q10 = Inlines.silk_SUB16((short) (in_Q10), (short) (pred_Q10));
        ind_tmp = Inlines.silk_SMULWB((int) inv_quant_step_size_Q6, res_Q10);
        ind_tmp = Inlines.silk_LIMIT(ind_tmp, 0 - SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT,
            SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT - 1);
        ind[j][i] = (byte) ind_tmp;
        rates_Q5 = ec_ix[i] + ind_tmp;

        // compute outputs for ind_tmp and ind_tmp + 1
        out0_Q10 = out0_Q10_table[ind_tmp + SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT];
        out1_Q10 = out1_Q10_table[ind_tmp + SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT];

        out0_Q10 = Inlines.silk_ADD16((short) (out0_Q10), (short) (pred_Q10));
        out1_Q10 = Inlines.silk_ADD16((short) (out1_Q10), (short) (pred_Q10));
        prev_out_Q10[j] = (short) (out0_Q10);
        prev_out_Q10[j + nStates] = (short) (out1_Q10);

        // compute RD for ind_tmp and ind_tmp + 1
        if (ind_tmp + 1 >= SilkConstants.NLSF_QUANT_MAX_AMPLITUDE) {
          if (ind_tmp + 1 == SilkConstants.NLSF_QUANT_MAX_AMPLITUDE) {
            rate0_Q5 = ec_rates_Q5[rates_Q5 + SilkConstants.NLSF_QUANT_MAX_AMPLITUDE];
            rate1_Q5 = 280;
          } else {
            rate0_Q5 = Inlines.silk_SMLABB(280 - (43 * SilkConstants.NLSF_QUANT_MAX_AMPLITUDE), 43,
                ind_tmp);
            rate1_Q5 = Inlines.silk_ADD16((short) (rate0_Q5), (short) 43);
          }
        } else if (ind_tmp <= 0 - SilkConstants.NLSF_QUANT_MAX_AMPLITUDE) {
          if (ind_tmp == 0 - SilkConstants.NLSF_QUANT_MAX_AMPLITUDE) {
            rate0_Q5 = 280;
            rate1_Q5 = ec_rates_Q5[rates_Q5 + 1 + SilkConstants.NLSF_QUANT_MAX_AMPLITUDE];
          } else {
            rate0_Q5 = Inlines.silk_SMLABB(280 - 43 * SilkConstants.NLSF_QUANT_MAX_AMPLITUDE, -43,
                ind_tmp);
            rate1_Q5 = Inlines.silk_SUB16((short) (rate0_Q5), (short) 43);
          }
        } else {
          rate0_Q5 = ec_rates_Q5[rates_Q5 + SilkConstants.NLSF_QUANT_MAX_AMPLITUDE];
          rate1_Q5 = ec_rates_Q5[rates_Q5 + 1 + SilkConstants.NLSF_QUANT_MAX_AMPLITUDE];
        }

        RD_tmp_Q25 = RD_Q25[j];
        diff_Q10 = Inlines.silk_SUB16((short) (in_Q10), (short) (out0_Q10));
        RD_Q25[j] = Inlines.silk_SMLABB(
            Inlines.silk_MLA(RD_tmp_Q25, Inlines.silk_SMULBB(diff_Q10, diff_Q10), w_Q5[i]), mu_Q20,
            rate0_Q5);
        diff_Q10 = Inlines.silk_SUB16((short) (in_Q10), (short) (out1_Q10));
        RD_Q25[j + nStates] = Inlines.silk_SMLABB(
            Inlines.silk_MLA(RD_tmp_Q25, Inlines.silk_SMULBB(diff_Q10, diff_Q10), w_Q5[i]), mu_Q20,
            rate1_Q5);
      }

      if (nStates <= (SilkConstants.NLSF_QUANT_DEL_DEC_STATES >> 1)) {
        // double number of states and copy
        for (j = 0; j < nStates; j++) {
          ind[j + nStates][i] = (byte) (ind[j][i] + 1);
        }
        nStates = Inlines.silk_LSHIFT(nStates, 1);

        for (j = nStates; j < SilkConstants.NLSF_QUANT_DEL_DEC_STATES; j++) {
          ind[j][i] = ind[j - nStates][i];
        }
      } else if (i > 0) {
        // sort lower and upper half of RD_Q25, pairwise
        for (j = 0; j < SilkConstants.NLSF_QUANT_DEL_DEC_STATES; j++) {
          if (RD_Q25[j] > RD_Q25[j + SilkConstants.NLSF_QUANT_DEL_DEC_STATES]) {
            RD_max_Q25[j] = RD_Q25[j];
            RD_min_Q25[j] = RD_Q25[j + SilkConstants.NLSF_QUANT_DEL_DEC_STATES];
            RD_Q25[j] = RD_min_Q25[j];
            RD_Q25[j + SilkConstants.NLSF_QUANT_DEL_DEC_STATES] = RD_max_Q25[j];

            // swap prev_out values
            out0_Q10 = prev_out_Q10[j];
            prev_out_Q10[j] = prev_out_Q10[j + SilkConstants.NLSF_QUANT_DEL_DEC_STATES];
            prev_out_Q10[j + SilkConstants.NLSF_QUANT_DEL_DEC_STATES] = (short) (out0_Q10);
            ind_sort[j] = j + SilkConstants.NLSF_QUANT_DEL_DEC_STATES;
          } else {
            RD_min_Q25[j] = RD_Q25[j];
            RD_max_Q25[j] = RD_Q25[j + SilkConstants.NLSF_QUANT_DEL_DEC_STATES];
            ind_sort[j] = j;
          }
        }

        // compare the highest RD values of the winning half with the lowest one in the losing half,
        // and copy if necessary
        // afterwards ind_sort[] will contain the indices of the NLSF_QUANT_DEL_DEC_STATES winning
        // RD values
        while (true) {
          min_max_Q25 = Integer.MAX_VALUE;
          max_min_Q25 = 0;
          ind_min_max = 0;
          ind_max_min = 0;

          for (j = 0; j < SilkConstants.NLSF_QUANT_DEL_DEC_STATES; j++) {
            if (min_max_Q25 > RD_max_Q25[j]) {
              min_max_Q25 = RD_max_Q25[j];
              ind_min_max = j;
            }
            if (max_min_Q25 < RD_min_Q25[j]) {
              max_min_Q25 = RD_min_Q25[j];
              ind_max_min = j;
            }
          }

          if (min_max_Q25 >= max_min_Q25) {
            break;
          }

          // copy ind_min_max to ind_max_min
          ind_sort[ind_max_min] = ind_sort[ind_min_max] ^ SilkConstants.NLSF_QUANT_DEL_DEC_STATES;
          RD_Q25[ind_max_min] = RD_Q25[ind_min_max + SilkConstants.NLSF_QUANT_DEL_DEC_STATES];
          prev_out_Q10[ind_max_min] =
              prev_out_Q10[ind_min_max + SilkConstants.NLSF_QUANT_DEL_DEC_STATES];
          RD_min_Q25[ind_max_min] = 0;
          RD_max_Q25[ind_min_max] = Integer.MAX_VALUE;
          System.arraycopy(ind[ind_min_max], 0, ind[ind_max_min], 0, order);
        }

        // increment index if it comes from the upper half
        for (j = 0; j < SilkConstants.NLSF_QUANT_DEL_DEC_STATES; j++) {
          byte x =
              (byte) Inlines.silk_RSHIFT(ind_sort[j], SilkConstants.NLSF_QUANT_DEL_DEC_STATES_LOG2);
          ind[j][i] += x;
        }
      } else {
        // i == 0
        break;
      }
    }

    // last sample: find winner, copy indices and return RD value
    ind_tmp = 0;
    min_Q25 = Integer.MAX_VALUE;
    for (j = 0; j < 2 * SilkConstants.NLSF_QUANT_DEL_DEC_STATES; j++) {
      if (min_Q25 > RD_Q25[j]) {
        min_Q25 = RD_Q25[j];
        ind_tmp = j;
      }
    }

    for (j = 0; j < order; j++) {
      indices[j] = ind[ind_tmp & (SilkConstants.NLSF_QUANT_DEL_DEC_STATES - 1)][j];
      Inlines.OpusAssert(indices[j] >= 0 - SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT);
      Inlines.OpusAssert(indices[j] <= SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT);
    }

    indices[0] = (byte) (indices[0]
        + Inlines.silk_RSHIFT(ind_tmp, SilkConstants.NLSF_QUANT_DEL_DEC_STATES_LOG2));
    Inlines.OpusAssert(indices[0] <= SilkConstants.NLSF_QUANT_MAX_AMPLITUDE_EXT);
    Inlines.OpusAssert(min_Q25 >= 0);
    return min_Q25;
  }

  /// <summary>
  /// NLSF vector encoder
  /// </summary>
  /// <param name="NLSFIndices">(I) Codebook path vector [ LPC_ORDER + 1 ]</param>
  /// <param name="pNLSF_Q15">(I/O) Quantized NLSF vector [ LPC_ORDER ]</param>
  /// <param name="psNLSF_CB">(I) Codebook object</param>
  /// <param name="pW_QW">(I) NLSF weight vector [ LPC_ORDER ]</param>
  /// <param name="NLSF_mu_Q20">(I) Rate weight for the RD optimization</param>
  /// <param name="nSurvivors">(I) Max survivors after first stage</param>
  /// <param name="signalType">(I) Signal type: 0/1/2</param>
  /// <returns>RD value in Q25</returns>
  static int silk_NLSF_encode(
      byte[] NLSFIndices,
      short[] pNLSF_Q15,
      NLSFCodebook psNLSF_CB,
      short[] pW_QW,
      int NLSF_mu_Q20,
      int nSurvivors,
      int signalType) {
    int i, s, ind1, prob_Q8, bits_q7;
    int W_tmp_Q9;
    int[] err_Q26;
    int[] RD_Q25;
    int[] tempIndices1;
    byte[][] tempIndices2;
    short[] res_Q15 = new short[psNLSF_CB.order];
    short[] res_Q10 = new short[psNLSF_CB.order];
    short[] NLSF_tmp_Q15 = new short[psNLSF_CB.order];
    short[] W_tmp_QW = new short[psNLSF_CB.order];
    short[] W_adj_Q5 = new short[psNLSF_CB.order];
    short[] pred_Q8 = new short[psNLSF_CB.order];
    short[] ec_ix = new short[psNLSF_CB.order];
    short[] pCB = psNLSF_CB.CB1_NLSF_Q8;
    int iCDF_ptr;
    int pCB_element;

    Inlines.OpusAssert(nSurvivors <= SilkConstants.NLSF_VQ_MAX_SURVIVORS);
    Inlines.OpusAssert(signalType >= 0 && signalType <= 2);
    Inlines.OpusAssert(NLSF_mu_Q20 <= 32767 && NLSF_mu_Q20 >= 0);

    // NLSF stabilization
    silk_NLSF_stabilize(pNLSF_Q15, psNLSF_CB.deltaMin_Q15, psNLSF_CB.order);

    // First stage: VQ
    err_Q26 = new int[psNLSF_CB.nVectors];
    silk_NLSF_VQ(err_Q26, pNLSF_Q15, psNLSF_CB.CB1_NLSF_Q8, psNLSF_CB.nVectors, psNLSF_CB.order);

    // Sort the quantization errors
    tempIndices1 = new int[nSurvivors];
    Sort.silk_insertion_sort_increasing(err_Q26, tempIndices1, psNLSF_CB.nVectors, nSurvivors);

    RD_Q25 = new int[nSurvivors];
    tempIndices2 = Arrays.InitTwoDimensionalArrayByte(nSurvivors, SilkConstants.MAX_LPC_ORDER);

    // Loop over survivors
    for (s = 0; s < nSurvivors; s++) {
      ind1 = tempIndices1[s];

      // Residual after first stage
      pCB_element = ind1 * psNLSF_CB.order; // opt: potential 1:2 partitioned buffer
      for (i = 0; i < psNLSF_CB.order; i++) {
        NLSF_tmp_Q15[i] = Inlines.silk_LSHIFT16((short) pCB[pCB_element + i], 7);
        res_Q15[i] = (short) (pNLSF_Q15[i] - NLSF_tmp_Q15[i]);
      }

      // Weights from codebook vector
      silk_NLSF_VQ_weights_laroia(W_tmp_QW, NLSF_tmp_Q15, psNLSF_CB.order);

      // Apply square-rooted weights
      for (i = 0; i < psNLSF_CB.order; i++) {
        W_tmp_Q9 = Inlines
            .silk_SQRT_APPROX(Inlines.silk_LSHIFT((int) W_tmp_QW[i], 18 - SilkConstants.NLSF_W_Q));
        res_Q10[i] = (short) Inlines.silk_RSHIFT(Inlines.silk_SMULBB(res_Q15[i], W_tmp_Q9), 14);
      }

      // Modify input weights accordingly
      for (i = 0; i < psNLSF_CB.order; i++) {
        W_adj_Q5[i] =
            (short) (Inlines.silk_DIV32_16(Inlines.silk_LSHIFT((int) pW_QW[i], 5), W_tmp_QW[i]));
      }

      // Unpack entropy table indices and predictor for current CB1 index
      silk_NLSF_unpack(ec_ix, pred_Q8, psNLSF_CB, ind1);

      // Trellis quantizer
      RD_Q25[s] = silk_NLSF_del_dec_quant(
          tempIndices2[s],
          res_Q10,
          W_adj_Q5,
          pred_Q8,
          ec_ix,
          psNLSF_CB.ec_Rates_Q5,
          psNLSF_CB.quantStepSize_Q16,
          psNLSF_CB.invQuantStepSize_Q6,
          NLSF_mu_Q20,
          psNLSF_CB.order);

      // Add rate for first stage
      iCDF_ptr = (signalType >> 1) * psNLSF_CB.nVectors;

      if (ind1 == 0) {
        prob_Q8 = 256 - psNLSF_CB.CB1_iCDF[iCDF_ptr + ind1];
      } else {
        prob_Q8 = psNLSF_CB.CB1_iCDF[iCDF_ptr + ind1 - 1] - psNLSF_CB.CB1_iCDF[iCDF_ptr + ind1];
      }

      bits_q7 = (8 << 7) - Inlines.silk_lin2log(prob_Q8);
      RD_Q25[s] = Inlines.silk_SMLABB(RD_Q25[s], bits_q7, Inlines.silk_RSHIFT(NLSF_mu_Q20, 2));
    }

    // Find the lowest rate-distortion error
    int[] bestIndex = new int[1];
    Sort.silk_insertion_sort_increasing(RD_Q25, bestIndex, nSurvivors, 1);

    NLSFIndices[0] = (byte) tempIndices1[bestIndex[0]];
    System.arraycopy(tempIndices2[bestIndex[0]], 0, NLSFIndices, 1, psNLSF_CB.order);

    // Decode
    silk_NLSF_decode(pNLSF_Q15, NLSFIndices, psNLSF_CB);

    return RD_Q25[0];
  }

  /// <summary>
  /// helper function for NLSF2A(..)
  /// </summary>
  /// <param name="o">(O) intermediate polynomial, QA [dd+1]</param>
  /// <param name="cLSF">(I) vector of interleaved 2*cos(LSFs), QA [d]</param>
  /// <param name="dd">(I) polynomial order (= 1/2 * filter order)</param>
  static void silk_NLSF2A_find_poly(
      int[] o,
      int[] cLSF,
      int cLSF_ptr,
      int dd) {
    int k, n, ftmp;

    o[0] = Inlines.silk_LSHIFT(1, QA);
    o[1] = 0 - cLSF[cLSF_ptr];
    for (k = 1; k < dd; k++) {
      ftmp = cLSF[cLSF_ptr + (2 * k)];
      /* QA */
      o[k + 1] = Inlines.silk_LSHIFT(o[k - 1], 1)
          - (int) Inlines.silk_RSHIFT_ROUND64(Inlines.silk_SMULL(ftmp, o[k]), QA);
      for (n = k; n > 1; n--) {
        o[n] +=
            o[n - 2] - (int) Inlines.silk_RSHIFT_ROUND64(Inlines.silk_SMULL(ftmp, o[n - 1]), QA);
      }
      o[1] -= ftmp;
    }
  }

  /*
   * This ordering was found to maximize quality. It improves numerical accuracy of
   * silk_NLSF2A_find_poly() compared to "standard" ordering.
   */
  private static final byte[] ordering16 = {0, 15, 8, 7, 4, 11, 12, 3, 2, 13, 10, 5, 6, 9, 14, 1};
  private static final byte[] ordering10 = {0, 9, 6, 3, 4, 5, 8, 1, 2, 7};

  /// <summary>
  /// compute whitening filter coefficients from normalized line spectral frequencies
  /// </summary>
  /// <param name="a_Q12">(O) monic whitening filter coefficients in Q12, [ d ]</param>
  /// <param name="NLSF">(I) normalized line spectral frequencies in Q15, [ d ]</param>
  /// <param name="d">(I) filter order (should be even)</param>
  static void silk_NLSF2A(
      short[] a_Q12,
      short[] NLSF,
      int d) {

    byte[] ordering;
    int k, i, dd;
    int[] cos_LSF_QA = new int[d];
    int[] P = new int[d / 2 + 1];
    int[] Q = new int[d / 2 + 1];
    int[] a32_QA1 = new int[d];

    int Ptmp, Qtmp, f_int, f_frac, cos_val, delta;
    int maxabs, absval, idx = 0, sc_Q16;

    Inlines.OpusAssert(SilkConstants.LSF_COS_TAB_SZ == 128);
    Inlines.OpusAssert(d == 10 || d == 16);

    /* convert LSFs to 2*cos(LSF), using piecewise linear curve from table */
    ordering = d == 16 ? ordering16 : ordering10;

    for (k = 0; k < d; k++) {
      Inlines.OpusAssert(NLSF[k] >= 0);

      /* f_int on a scale 0-127 (rounded down) */
      f_int = Inlines.silk_RSHIFT(NLSF[k], 15 - 7);

      /* f_frac, range: 0..255 */
      f_frac = NLSF[k] - Inlines.silk_LSHIFT(f_int, 15 - 7);

      Inlines.OpusAssert(f_int >= 0);
      Inlines.OpusAssert(f_int < SilkConstants.LSF_COS_TAB_SZ);

      /* Read start and end value from table */
      cos_val = SilkTables.silk_LSFCosTab_Q12[f_int];
      /* Q12 */
      delta = SilkTables.silk_LSFCosTab_Q12[f_int + 1] - cos_val;
      /* Q12, with a range of 0..200 */

      /* Linear interpolation */
      cos_LSF_QA[ordering[k]] = Inlines.silk_RSHIFT_ROUND(
          Inlines.silk_LSHIFT(cos_val, 8) + Inlines.silk_MUL(delta, f_frac), 20 - QA);
      /* QA */
    }

    dd = Inlines.silk_RSHIFT(d, 1);

    /* generate even and odd polynomials using convolution */
    silk_NLSF2A_find_poly(P, cos_LSF_QA, 0, dd);
    silk_NLSF2A_find_poly(Q, cos_LSF_QA, 1, dd);

    /* convert even and odd polynomials to opus_int32 Q12 filter coefs */
    for (k = 0; k < dd; k++) {
      Ptmp = P[k + 1] + P[k];
      Qtmp = Q[k + 1] - Q[k];

      /* the Ptmp and Qtmp values at this stage need to fit in int32 */
      a32_QA1[k] = -Qtmp - Ptmp;
      /* QA+1 */
      a32_QA1[d - k - 1] = Qtmp - Ptmp;
      /* QA+1 */
    }

    /*
     * Limit the maximum absolute value of the prediction coefficients, so that they'll fit in int16
     */
    for (i = 0; i < 10; i++) {
      /* Find maximum absolute value and its index */
      maxabs = 0;
      for (k = 0; k < d; k++) {
        absval = Inlines.silk_abs(a32_QA1[k]);
        if (absval > maxabs) {
          maxabs = absval;
          idx = k;
        }
      }

      maxabs = Inlines.silk_RSHIFT_ROUND(maxabs, QA + 1 - 12);
      /* QA+1 . Q12 */

      if (maxabs > Short.MAX_VALUE) {
        /* Reduce magnitude of prediction coefficients */
        maxabs = Inlines.silk_min(maxabs, 163838);
        /* ( silk_int32_MAX >> 14 ) + silk_int16_MAX = 163838 */
        sc_Q16 = ((int) ((0.999f) * ((long) 1 << (16)) + 0.5))
            /* Inlines.SILK_CONST(0.999f, 16) */ - Inlines.silk_DIV32(
                Inlines.silk_LSHIFT(maxabs - Short.MAX_VALUE, 14),
                Inlines.silk_RSHIFT32(Inlines.silk_MUL(maxabs, idx + 1), 2));
        Filters.silk_bwexpander_32(a32_QA1, d, sc_Q16);
      } else {
        break;
      }
    }

    if (i == 10) {
      /* Reached the last iteration, clip the coefficients */
      for (k = 0; k < d; k++) {
        a_Q12[k] = (short) Inlines.silk_SAT16(Inlines.silk_RSHIFT_ROUND(a32_QA1[k], QA + 1 - 12));
        /* QA+1 . Q12 */
        a32_QA1[k] = Inlines.silk_LSHIFT((int) a_Q12[k], QA + 1 - 12);
      }
    } else {
      for (k = 0; k < d; k++) {
        a_Q12[k] = (short) Inlines.silk_RSHIFT_ROUND(a32_QA1[k], QA + 1 - 12);
        /* QA+1 . Q12 */
      }
    }

    for (i = 0; i < SilkConstants.MAX_LPC_STABILIZE_ITERATIONS; i++) {
      if (Filters.silk_LPC_inverse_pred_gain(a_Q12,
          d) < ((int) ((1.0f / SilkConstants.MAX_PREDICTION_POWER_GAIN) * ((long) 1 << (30))
              + 0.5))/* Inlines.SILK_CONST(1.0f / SilkConstants.MAX_PREDICTION_POWER_GAIN, 30) */) {
        /* Prediction coefficients are (too close to) unstable; apply bandwidth expansion */
        /* on the unscaled coefficients, convert to Q12 and measure again */
        Filters.silk_bwexpander_32(a32_QA1, d, 65536 - Inlines.silk_LSHIFT(2, i));

        for (k = 0; k < d; k++) {
          a_Q12[k] = (short) Inlines.silk_RSHIFT_ROUND(a32_QA1[k], QA + 1 - 12);
          /* QA+1 . Q12 */
        }
      } else {
        break;
      }
    }
  }

  /// <summary>
  /// Helper function for A2NLSF(..) Transforms polynomials from cos(n*f) to cos(f)^n
  /// </summary>
  /// <param name="p">(I/O) Polynomial</param>
  /// <param name="dd">(I) Polynomial order (= filter order / 2 )</param>
  static void silk_A2NLSF_trans_poly(int[] p, int dd) {
    int k, n;

    for (k = 2; k <= dd; k++) {
      for (n = dd; n > k; n--) {
        p[n - 2] -= p[n];
      }
      p[k - 2] -= Inlines.silk_LSHIFT(p[k], 1);
    }
  }

  /// <summary>
  /// Helper function for A2NLSF(..) Polynomial evaluation
  /// </summary>
  /// <param name="p">(I) Polynomial, Q16</param>
  /// <param name="x">(I) Evaluation point, Q12</param>
  /// <param name="dd">(I) Order</param>
  /// <returns>the polynomial evaluation, in Q16</returns>
  static int silk_A2NLSF_eval_poly(int[] p, int x, int dd) {
    int n;
    int x_Q16, y32;

    y32 = p[dd];
    /* Q16 */
    x_Q16 = Inlines.silk_LSHIFT(x, 4);

    if (8 == dd) {
      y32 = Inlines.silk_SMLAWW(p[7], y32, x_Q16);
      y32 = Inlines.silk_SMLAWW(p[6], y32, x_Q16);
      y32 = Inlines.silk_SMLAWW(p[5], y32, x_Q16);
      y32 = Inlines.silk_SMLAWW(p[4], y32, x_Q16);
      y32 = Inlines.silk_SMLAWW(p[3], y32, x_Q16);
      y32 = Inlines.silk_SMLAWW(p[2], y32, x_Q16);
      y32 = Inlines.silk_SMLAWW(p[1], y32, x_Q16);
      y32 = Inlines.silk_SMLAWW(p[0], y32, x_Q16);
    } else {
      for (n = dd - 1; n >= 0; n--) {
        y32 = Inlines.silk_SMLAWW(p[n], y32, x_Q16);
        /* Q16 */
      }
    }

    return y32;
  }

  static void silk_A2NLSF_init(
      int[] a_Q16,
      int[] P,
      int[] Q,
      int dd) {
    int k;

    /* Convert filter coefs to even and odd polynomials */
    P[dd] = Inlines.silk_LSHIFT(1, 16);
    Q[dd] = Inlines.silk_LSHIFT(1, 16);
    for (k = 0; k < dd; k++) {
      P[k] = -a_Q16[dd - k - 1] - a_Q16[dd + k];
      /* Q16 */
      Q[k] = -a_Q16[dd - k - 1] + a_Q16[dd + k];
      /* Q16 */
    }

    /* Divide out zeros as we have that for even filter orders, */
    /* z = 1 is always a root in Q, and */
    /* z = -1 is always a root in P */
    for (k = dd; k > 0; k--) {
      P[k - 1] -= P[k];
      Q[k - 1] += Q[k];
    }

    /* Transform polynomials from cos(n*f) to cos(f)^n */
    silk_A2NLSF_trans_poly(P, dd);
    silk_A2NLSF_trans_poly(Q, dd);
  }

  /// <summary>
  /// Compute Normalized Line Spectral Frequencies (NLSFs) from whitening filter coefficients
  /// If not all roots are found, the a_Q16 coefficients are bandwidth expanded until convergence.
  /// </summary>
  /// <param name="NLSF">(O) Normalized Line Spectral Frequencies in Q15 (0..2^15-1) [d]</param>
  /// <param name="a_Q16">(I/O) Monic whitening filter coefficients in Q16 [d]</param>
  /// <param name="d">(I) Filter order (must be even)</param>
  static void silk_A2NLSF(short[] NLSF, int[] a_Q16, int d) {
    int i, k, m, dd, root_ix, ffrac;
    int xlo, xhi, xmid;
    int ylo, yhi, ymid, thr;
    int nom, den;
    int[] P = new int[SilkConstants.SILK_MAX_ORDER_LPC / 2 + 1];
    int[] Q = new int[SilkConstants.SILK_MAX_ORDER_LPC / 2 + 1];
    int[][] PQ = new int[2][];
    int[] p;

    /* Store pointers to array */
    PQ[0] = P;
    PQ[1] = Q;

    dd = Inlines.silk_RSHIFT(d, 1);

    silk_A2NLSF_init(a_Q16, P, Q, dd);

    /* Find roots, alternating between P and Q */
    p = P;
    /* Pointer to polynomial */

    xlo = SilkTables.silk_LSFCosTab_Q12[0];
    /* Q12 */
    ylo = silk_A2NLSF_eval_poly(p, xlo, dd);

    if (ylo < 0) {
      /* Set the first NLSF to zero and move on to the next */
      NLSF[0] = 0;
      p = Q;
      /* Pointer to polynomial */
      ylo = silk_A2NLSF_eval_poly(p, xlo, dd);
      root_ix = 1;
      /* Index of current root */
    } else {
      root_ix = 0;
      /* Index of current root */
    }
    k = 1;
    /* Loop counter */
    i = 0;
    /* Counter for bandwidth expansions applied */
    thr = 0;
    while (true) {
      /* Evaluate polynomial */
      xhi = SilkTables.silk_LSFCosTab_Q12[k];
      /* Q12 */
      yhi = silk_A2NLSF_eval_poly(p, xhi, dd);

      /* Detect zero crossing */
      if ((ylo <= 0 && yhi >= thr) || (ylo >= 0 && yhi <= -thr)) {
        if (yhi == 0) {
          /* If the root lies exactly at the end of the current */
          /* interval, look for the next root in the next interval */
          thr = 1;
        } else {
          thr = 0;
        }
        /* Binary division */
        ffrac = -256;
        for (m = 0; m < BIN_DIV_STEPS_A2NLSF; m++) {
          /* Evaluate polynomial */
          xmid = Inlines.silk_RSHIFT_ROUND(xlo + xhi, 1);
          ymid = silk_A2NLSF_eval_poly(p, xmid, dd);

          /* Detect zero crossing */
          if ((ylo <= 0 && ymid >= 0) || (ylo >= 0 && ymid <= 0)) {
            /* Reduce frequency */
            xhi = xmid;
            yhi = ymid;
          } else {
            /* Increase frequency */
            xlo = xmid;
            ylo = ymid;
            ffrac = Inlines.silk_ADD_RSHIFT(ffrac, 128, m);
          }
        }

        /* Interpolate */
        if (Inlines.silk_abs(ylo) < 65536) {
          /* Avoid dividing by zero */
          den = ylo - yhi;
          nom = Inlines.silk_LSHIFT(ylo, 8 - BIN_DIV_STEPS_A2NLSF) + Inlines.silk_RSHIFT(den, 1);
          if (den != 0) {
            ffrac += Inlines.silk_DIV32(nom, den);
          }
        } else {
          /* No risk of dividing by zero because abs(ylo - yhi) >= abs(ylo) >= 65536 */
          ffrac +=
              Inlines.silk_DIV32(ylo, Inlines.silk_RSHIFT(ylo - yhi, 8 - BIN_DIV_STEPS_A2NLSF));
        }
        NLSF[root_ix] =
            (short) Inlines.silk_min_32(Inlines.silk_LSHIFT((int) k, 8) + ffrac, Short.MAX_VALUE);

        Inlines.OpusAssert(NLSF[root_ix] >= 0);

        root_ix++;
        /* Next root */
        if (root_ix >= d) {
          /* Found all roots */
          break;
        }

        /* Alternate pointer to polynomial */
        p = PQ[root_ix & 1];

        /* Evaluate polynomial */
        xlo = SilkTables.silk_LSFCosTab_Q12[k - 1];
        /* Q12 */
        ylo = Inlines.silk_LSHIFT(1 - (root_ix & 2), 12);
      } else {
        /* Increment loop counter */
        k++;
        xlo = xhi;
        ylo = yhi;
        thr = 0;

        if (k > SilkConstants.LSF_COS_TAB_SZ) {
          i++;
          if (i > MAX_ITERATIONS_A2NLSF) {
            /* Set NLSFs to white spectrum and exit */
            NLSF[0] = (short) Inlines.silk_DIV32_16(1 << 15, (short) (d + 1));
            for (k = 1; k < d; k++) {
              NLSF[k] = (short) Inlines.silk_SMULBB(k + 1, NLSF[0]);
            }
            return;
          }

          /* Error: Apply progressively more bandwidth expansion and run again */
          Filters.silk_bwexpander_32(a_Q16, d, 65536 - Inlines.silk_SMULBB(10 + i, i));
          /* 10_Q16 = 0.00015 */

          silk_A2NLSF_init(a_Q16, P, Q, dd);
          p = P;
          /* Pointer to polynomial */
          xlo = SilkTables.silk_LSFCosTab_Q12[0];
          /* Q12 */
          ylo = silk_A2NLSF_eval_poly(p, xlo, dd);
          if (ylo < 0) {
            /* Set the first NLSF to zero and move on to the next */
            NLSF[0] = 0;
            p = Q;
            /* Pointer to polynomial */
            ylo = silk_A2NLSF_eval_poly(p, xlo, dd);
            root_ix = 1;
            /* Index of current root */
          } else {
            root_ix = 0;
            /* Index of current root */
          }
          k = 1;
          /* Reset loop counter */
        }
      }
    }
  }

  /// <summary>
  /// Limit, stabilize, convert and quantize NLSFs
  /// </summary>
  /// <param name="psEncC">I/O Encoder state</param>
  /// <param name="PredCoef_Q12">O Prediction coefficients [ 2 ][MAX_LPC_ORDER]</param>
  /// <param name="pNLSF_Q15">I/O Normalized LSFs (quant out) (0 - (2^15-1)) [MAX_LPC_ORDER]</param>
  /// <param name="prev_NLSFq_Q15">I Previous Normalized LSFs (0 - (2^15-1)) [MAX_LPC_ORDER]</param>
  static void silk_process_NLSFs(
      SilkChannelEncoder psEncC,
      short[][] PredCoef_Q12,
      short[] pNLSF_Q15,
      short[] prev_NLSFq_Q15) {
    int i;
    boolean doInterpolate;
    int NLSF_mu_Q20;
    int i_sqr_Q15;
    short[] pNLSF0_temp_Q15 = new short[SilkConstants.MAX_LPC_ORDER];
    short[] pNLSFW_QW = new short[SilkConstants.MAX_LPC_ORDER];
    short[] pNLSFW0_temp_QW = new short[SilkConstants.MAX_LPC_ORDER];

    Inlines.OpusAssert(psEncC.speech_activity_Q8 >= 0);
    Inlines.OpusAssert(psEncC.speech_activity_Q8 <= ((int) ((1.0f) * ((long) 1 << (8)) + 0.5))/*
                                                                                               * Inlines
                                                                                               * .
                                                                                               * SILK_CONST
                                                                                               * (1.
                                                                                               * 0f,
                                                                                               * 8)
                                                                                               */);
    Inlines.OpusAssert(
        psEncC.useInterpolatedNLSFs == 1 || psEncC.indices.NLSFInterpCoef_Q2 == (1 << 2));

    /**
     * ********************
     */
    /* Calculate mu values */
    /**
     * ********************
     */
    /* NLSF_mu = 0.003 - 0.0015 * psEnc.speech_activity; */
    NLSF_mu_Q20 = Inlines.silk_SMLAWB(
        ((int) ((0.003f) * ((long) 1 << (20)) + 0.5))/* Inlines.SILK_CONST(0.003f, 20) */,
        ((int) ((-0.001f) * ((long) 1 << (28)) + 0.5))/* Inlines.SILK_CONST(-0.001f, 28) */,
        psEncC.speech_activity_Q8);
    if (psEncC.nb_subfr == 2) {
      /* Multiply by 1.5 for 10 ms packets */
      NLSF_mu_Q20 = Inlines.silk_ADD_RSHIFT(NLSF_mu_Q20, NLSF_mu_Q20, 1);
    }

    Inlines.OpusAssert(NLSF_mu_Q20 > 0);
    Inlines.OpusAssert(NLSF_mu_Q20 <= ((int) ((0.005f) * ((long) 1 << (20)) + 0.5))/*
                                                                                    * Inlines.
                                                                                    * SILK_CONST(0.
                                                                                    * 005f, 20)
                                                                                    */);

    /* Calculate NLSF weights */
    silk_NLSF_VQ_weights_laroia(pNLSFW_QW, pNLSF_Q15, psEncC.predictLPCOrder);

    /* Update NLSF weights for interpolated NLSFs */
    doInterpolate = (psEncC.useInterpolatedNLSFs == 1) && (psEncC.indices.NLSFInterpCoef_Q2 < 4);
    if (doInterpolate) {
      /* Calculate the interpolated NLSF vector for the first half */
      Inlines.silk_interpolate(pNLSF0_temp_Q15, prev_NLSFq_Q15, pNLSF_Q15,
          psEncC.indices.NLSFInterpCoef_Q2, psEncC.predictLPCOrder);

      /* Calculate first half NLSF weights for the interpolated NLSFs */
      silk_NLSF_VQ_weights_laroia(pNLSFW0_temp_QW, pNLSF0_temp_Q15, psEncC.predictLPCOrder);

      /* Update NLSF weights with contribution from first half */
      i_sqr_Q15 = Inlines.silk_LSHIFT(
          Inlines.silk_SMULBB(psEncC.indices.NLSFInterpCoef_Q2, psEncC.indices.NLSFInterpCoef_Q2),
          11);

      for (i = 0; i < psEncC.predictLPCOrder; i++) {
        pNLSFW_QW[i] = (short) (Inlines.silk_SMLAWB(Inlines.silk_RSHIFT(pNLSFW_QW[i], 1),
            (int) pNLSFW0_temp_QW[i], i_sqr_Q15));
        Inlines.OpusAssert(pNLSFW_QW[i] >= 1);
      }
    }

    //////////////////////////////////////////////////////////////////////////
    silk_NLSF_encode(psEncC.indices.NLSFIndices, pNLSF_Q15, psEncC.psNLSF_CB, pNLSFW_QW,
        NLSF_mu_Q20, psEncC.NLSF_MSVQ_Survivors, psEncC.indices.signalType);

    /* Convert quantized NLSFs back to LPC coefficients */
    silk_NLSF2A(PredCoef_Q12[1], pNLSF_Q15, psEncC.predictLPCOrder);

    if (doInterpolate) {
      /* Calculate the interpolated, quantized LSF vector for the first half */
      Inlines.silk_interpolate(pNLSF0_temp_Q15, prev_NLSFq_Q15, pNLSF_Q15,
          psEncC.indices.NLSFInterpCoef_Q2, psEncC.predictLPCOrder);

      /* Convert back to LPC coefficients */
      silk_NLSF2A(PredCoef_Q12[0], pNLSF0_temp_Q15, psEncC.predictLPCOrder);

    } else {
      /* Copy LPC coefficients for first half from second half */
      System.arraycopy(PredCoef_Q12[1], 0, PredCoef_Q12[0], 0, psEncC.predictLPCOrder);
    }
  }
}
